/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * DependencyVisualisationViewer.java
 *
 * Created on 16.04.2011, 12:22:02
 */

package dependencyviewer.ui;

import dependencyviewer.converter.ClassGraphConverter;
import dependencyviewer.converter.ContentProvider;
import dependencyviewer.converter.GraphConverter;
import dependencyviewer.converter.PackageGraphConverter;
import dependencyviewer.model.Element;
import dependencyviewer.model.util.ElementLabeller;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.SpringLayout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.GraphMouseListener;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import java.awt.event.MouseEvent;
import javax.swing.JOptionPane;

/**
 *
 * @author gbeine
 */
public class GraphVisualizationViewer extends javax.swing.JPanel {

    /** Creates new form DependencyVisualisationViewer */
    public GraphVisualizationViewer() {
        initComponents();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        bindingGroup = new org.jdesktop.beansbinding.BindingGroup();

        graphPopupMenu = new javax.swing.JPopupMenu();
        pickMenuItem = new javax.swing.JMenuItem();
        pickSeparator = new javax.swing.JPopupMenu.Separator();
        showOutboundMenuItem = new javax.swing.JMenuItem();
        hideOutboundMenuItem = new javax.swing.JMenuItem();
        outboundSeparator = new javax.swing.JPopupMenu.Separator();
        showInboundMenuItem = new javax.swing.JMenuItem();
        hideInoundMenuItem = new javax.swing.JMenuItem();
        inboundSeparator = new javax.swing.JPopupMenu.Separator();
        addToFilterMenuItem = new javax.swing.JMenuItem();
        filterListModel = new dependencyviewer.ui.FilterListModel();
        filterListPopupMenu = new javax.swing.JPopupMenu();
        removeElementsMenuItem = new javax.swing.JMenuItem();
        addFilterMenuItem = new javax.swing.JMenuItem();
        filterPanel = new javax.swing.JPanel();
        filterScrollPane = new javax.swing.JScrollPane();
        filterList = new javax.swing.JList();
        filterLabel = new javax.swing.JLabel();
        viewerPanel = new javax.swing.JPanel();

        graphPopupMenu.setName("graphPopupMenu"); // NOI18N

        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(dependencyviewer.DependencyViewerApp.class).getContext().getResourceMap(GraphVisualizationViewer.class);
        pickMenuItem.setText(resourceMap.getString("pickMenuItem.text")); // NOI18N
        pickMenuItem.setName("pickMenuItem"); // NOI18N
        pickMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                pickMenuItemActionPerformed(evt);
            }
        });
        graphPopupMenu.add(pickMenuItem);

        pickSeparator.setName("pickSeparator"); // NOI18N
        graphPopupMenu.add(pickSeparator);

        showOutboundMenuItem.setText(resourceMap.getString("showOutboundMenuItem.text")); // NOI18N
        showOutboundMenuItem.setActionCommand(resourceMap.getString("showOutboundMenuItem.actionCommand")); // NOI18N
        showOutboundMenuItem.setName("showOutboundMenuItem"); // NOI18N
        showOutboundMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                showOutboundMenuItemActionPerformed(evt);
            }
        });
        graphPopupMenu.add(showOutboundMenuItem);
        showOutboundMenuItem.getAccessibleContext().setAccessibleName(resourceMap.getString("showOutboundMenuItem.AccessibleContext.accessibleName")); // NOI18N

        hideOutboundMenuItem.setText(resourceMap.getString("hideOutboundMenuItem.text")); // NOI18N
        hideOutboundMenuItem.setName("hideOutboundMenuItem"); // NOI18N
        hideOutboundMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                hideOutboundMenuItemActionPerformed(evt);
            }
        });
        graphPopupMenu.add(hideOutboundMenuItem);

        outboundSeparator.setName("outboundSeparator"); // NOI18N
        graphPopupMenu.add(outboundSeparator);

        showInboundMenuItem.setText(resourceMap.getString("showInboundMenuItem.text")); // NOI18N
        showInboundMenuItem.setName("showInboundMenuItem"); // NOI18N
        showInboundMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                showInboundMenuItemActionPerformed(evt);
            }
        });
        graphPopupMenu.add(showInboundMenuItem);

        hideInoundMenuItem.setText(resourceMap.getString("hideInoundMenuItem.text")); // NOI18N
        hideInoundMenuItem.setName("hideInoundMenuItem"); // NOI18N
        hideInoundMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                hideInoundMenuItemActionPerformed(evt);
            }
        });
        graphPopupMenu.add(hideInoundMenuItem);

        inboundSeparator.setName("inboundSeparator"); // NOI18N
        graphPopupMenu.add(inboundSeparator);

        addToFilterMenuItem.setText(resourceMap.getString("addToFilterMenuItem.text")); // NOI18N
        addToFilterMenuItem.setName("addToFilterMenuItem"); // NOI18N
        addToFilterMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addToFilterMenuItemActionPerformed(evt);
            }
        });
        graphPopupMenu.add(addToFilterMenuItem);

        filterListPopupMenu.setName("filterListPopupMenu"); // NOI18N

        removeElementsMenuItem.setText(resourceMap.getString("removeElementsMenuItem.text")); // NOI18N
        removeElementsMenuItem.setName("removeElementsMenuItem"); // NOI18N
        removeElementsMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                removeElementsMenuItemActionPerformed(evt);
            }
        });
        filterListPopupMenu.add(removeElementsMenuItem);

        addFilterMenuItem.setText(resourceMap.getString("addFilterMenuItem.text")); // NOI18N
        addFilterMenuItem.setName("addFilterMenuItem"); // NOI18N
        addFilterMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addFilterMenuItemActionPerformed(evt);
            }
        });
        filterListPopupMenu.add(addFilterMenuItem);

        setName("Form"); // NOI18N

        filterPanel.setName("filterPanel"); // NOI18N

        filterScrollPane.setName("filterScrollPane"); // NOI18N

        filterList.setModel(filterListModel);
        filterList.setName("filterList"); // NOI18N

        org.jdesktop.beansbinding.Binding binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, filterListPopupMenu, org.jdesktop.beansbinding.ObjectProperty.create(), filterList, org.jdesktop.beansbinding.BeanProperty.create("componentPopupMenu"));
        bindingGroup.addBinding(binding);

        filterScrollPane.setViewportView(filterList);

        filterLabel.setText(resourceMap.getString("filterLabel.text")); // NOI18N
        filterLabel.setName("filterLabel"); // NOI18N

        javax.swing.GroupLayout filterPanelLayout = new javax.swing.GroupLayout(filterPanel);
        filterPanel.setLayout(filterPanelLayout);
        filterPanelLayout.setHorizontalGroup(
            filterPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(filterPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(filterPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(filterScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 104, Short.MAX_VALUE)
                    .addComponent(filterLabel))
                .addContainerGap())
        );
        filterPanelLayout.setVerticalGroup(
            filterPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(filterPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(filterLabel)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(filterScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 221, Short.MAX_VALUE)
                .addContainerGap())
        );

        viewerPanel.setName("viewerPanel"); // NOI18N

        javax.swing.GroupLayout viewerPanelLayout = new javax.swing.GroupLayout(viewerPanel);
        viewerPanel.setLayout(viewerPanelLayout);
        viewerPanelLayout.setHorizontalGroup(
            viewerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 294, Short.MAX_VALUE)
        );
        viewerPanelLayout.setVerticalGroup(
            viewerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 266, Short.MAX_VALUE)
        );

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addComponent(viewerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(filterPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(filterPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addComponent(viewerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        );

        bindingGroup.bind();
    }// </editor-fold>//GEN-END:initComponents

    private void showOutboundMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showOutboundMenuItemActionPerformed
        // If the selected item is not yet picked, pick it!
        if (!this.viewer.getPickedVertexState().isPicked(this.selected)) {
            this.viewer.getPickedVertexState().pick(this.selected, true);
        }
        // Pick all the neighbors of the selected item
        for (Element neighbor: this.graph.getSuccessors(this.selected)) {
            this.viewer.getPickedVertexState().pick(neighbor, true);
        }
    }//GEN-LAST:event_showOutboundMenuItemActionPerformed

    private void hideOutboundMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hideOutboundMenuItemActionPerformed
        // If the selected item is not yet picked, pick it!
        if (!this.viewer.getPickedVertexState().isPicked(this.selected)) {
            this.viewer.getPickedVertexState().pick(this.selected, true);
        }
        // Unpick all the neighbors of the selected item
        for (Element neighbor: this.graph.getSuccessors(this.selected)) {
            this.viewer.getPickedVertexState().pick(neighbor, false);
        }
    }//GEN-LAST:event_hideOutboundMenuItemActionPerformed

    private void showInboundMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showInboundMenuItemActionPerformed
        // If the selected item is not yet picked, pick it!
        if (!this.viewer.getPickedVertexState().isPicked(this.selected)) {
            this.viewer.getPickedVertexState().pick(this.selected, true);
        }
        // Pick all the neighbors of the selected item
        for (Element neighbor: this.graph.getPredecessors(this.selected)) {
            this.viewer.getPickedVertexState().pick(neighbor, true);
        }
    }//GEN-LAST:event_showInboundMenuItemActionPerformed

    private void hideInoundMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hideInoundMenuItemActionPerformed
        // If the selected item is not yet picked, pick it!
        if (!this.viewer.getPickedVertexState().isPicked(this.selected)) {
            this.viewer.getPickedVertexState().pick(this.selected, true);
        }
        // Unpick all the neighbors of the selected item
        for (Element neighbor: this.graph.getPredecessors(this.selected)) {
            this.viewer.getPickedVertexState().pick(neighbor, false);
        }
    }//GEN-LAST:event_hideInoundMenuItemActionPerformed

    private void addToFilterMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addToFilterMenuItemActionPerformed
        this.filterListModel = new FilterListModel(this.filterListModel);
        this.filterListModel.addElement(this.selected.getName());
        this.filterList.setModel(this.filterListModel);
        this.graph = null;
        this.repaintGraph();
    }//GEN-LAST:event_addToFilterMenuItemActionPerformed

    private void pickMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pickMenuItemActionPerformed
        // Toggle pick state of current selected element
        this.viewer.getPickedVertexState().pick(
            this.selected,
            !this.viewer.getPickedVertexState().isPicked(this.selected)
            );
    }//GEN-LAST:event_pickMenuItemActionPerformed

    private void removeElementsMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeElementsMenuItemActionPerformed
        for (Object o: this.filterList.getSelectedValues()) {
            this.filterListModel.removeElement((String)o);
        }
        this.filterListModel = new FilterListModel(this.filterListModel);
        this.filterList.setModel(this.filterListModel);
        this.graph = null;
        this.repaintGraph();
    }//GEN-LAST:event_removeElementsMenuItemActionPerformed

    private void addFilterMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addFilterMenuItemActionPerformed
        // TODO: replace with dialog supporting regular expressions
        String filterString = JOptionPane.showInputDialog(this, "Enter filter pattern:", "New filter pattern", 1);
        this.filterListModel = new FilterListModel(this.filterListModel);
        this.filterListModel.addElement(filterString);
        this.filterList.setModel(this.filterListModel);
        this.graph = null;
        this.repaintGraph();
    }//GEN-LAST:event_addFilterMenuItemActionPerformed

    /**
     * Access to the GraphMouse
     * @return
     */
    public ModalGraphMouse getGraphMouse() {
        return this.graphMouse;
    }

    /**
     * Set the Layout used for the graph
     * @param layoutCommand
     */
    public void setLayout(final SetLayoutCommand layoutCommand) {
        if (null != this.viewer) {
            this.viewer.setGraphLayout(layoutCommand.getLayout());
        }
    }

    public void setFilter(final Boolean filter) {
        this.filter = filter;
        this.graph = null;
        this.repaintGraph();
    }
    
    public void setLevel(final GraphVisualizationLevel level) {
        this.level = level;
        this.graph = null;
        this.repaintGraph();
    }

    /**
     * 
     * @param elements
     */
    public void setContentProvider(final ContentProvider contentProvider) {
        this.contentProvider = contentProvider;
        this.graph = null;
        this.repaintGraph();
    }

    private void repaintGraph() {
        if (this.contentProvider.size() > 0 && null == this.graph) {
            this.initGraph();
        }
        if (null != this.graph && null == this.viewer) {
            this.initVisualizationViewer();
        }
        if (null != this.graph && null != this.viewer) {
            this.viewer.getGraphLayout().setGraph(this.graph);
            this.viewer.repaint();
        }
    }

    private void initVisualizationViewer() {
        
        this.viewer = new VisualizationViewer<Element,Number>(new SpringLayout<Element, Number>(this.graph));

        this.viewer.setGraphMouse(this.graphMouse);
        this.viewer.setVertexToolTipTransformer(new ToStringLabeller<Element>());

        this.viewer.getRenderContext().setVertexFillPaintTransformer(
                new PickableVertexPaintTransformer<Element>(this.viewer.getPickedVertexState(), java.awt.Color.RED, java.awt.Color.YELLOW)
                );

        this.viewer.getRenderContext().setVertexLabelTransformer(
                new ElementLabeller()
                );

        // Forces size changes on the outer Panel to the VisualizationViewer
        this.viewer.addHierarchyBoundsListener(new java.awt.event.HierarchyBoundsListener() {
            public void ancestorMoved(java.awt.event.HierarchyEvent evt) {
            }
            public void ancestorResized(java.awt.event.HierarchyEvent evt) {
                // Reset the size only if the graphPanel's size has been changed
                if (java.awt.event.HierarchyEvent.ANCESTOR_RESIZED == evt.getID()) {
                    if (evt.getChanged().equals(GraphVisualizationViewer.this)) {
                        GraphVisualizationViewer.this.viewer.setSize(GraphVisualizationViewer.this.getSize());
                    }
                }
            }
        });

        this.viewer.addGraphMouseListener(new GraphMouseListener<Element>() {
            public void graphClicked(Element v, MouseEvent me) {
                if (MouseEvent.BUTTON3 == me.getButton()) {
                    GraphVisualizationViewer.this.selected = v;
                    GraphVisualizationViewer.this.graphPopupMenu.show(GraphVisualizationViewer.this.viewer, me.getX(), me.getY());
                }
            }
            public void graphPressed(Element v, MouseEvent me) {
            }
            public void graphReleased(Element v, MouseEvent me) {
            }
        });

        this.viewer.setSize(this.viewerPanel.getSize());
        this.viewerPanel.add(this.viewer, java.awt.BorderLayout.CENTER);
    }

    private void initGraph() {
        if (this.filter && GraphVisualizationLevel.PACKAGE.equals(this.level)) {
            this.graphConverter = new PackageGraphConverter(this.contentProvider.getPackages(), ((FilterListModel)this.filterList.getModel()).getPackageFilter());
        } else if (GraphVisualizationLevel.PACKAGE.equals(this.level)) {
            this.graphConverter = new PackageGraphConverter(this.contentProvider.getPackages());
        } else if (this.filter && GraphVisualizationLevel.CLASS.equals(this.level)) {
            this.graphConverter = new ClassGraphConverter(this.contentProvider.getClasses(), ((FilterListModel)this.filterList.getModel()).getPackageFilter());
        } else {
            this.graphConverter = new ClassGraphConverter(this.contentProvider.getClasses());
        }
        this.graph = this.graphConverter.createGraph();
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JMenuItem addFilterMenuItem;
    private javax.swing.JMenuItem addToFilterMenuItem;
    private javax.swing.JLabel filterLabel;
    private javax.swing.JList filterList;
    private dependencyviewer.ui.FilterListModel filterListModel;
    private javax.swing.JPopupMenu filterListPopupMenu;
    private javax.swing.JPanel filterPanel;
    private javax.swing.JScrollPane filterScrollPane;
    private javax.swing.JPopupMenu graphPopupMenu;
    private javax.swing.JMenuItem hideInoundMenuItem;
    private javax.swing.JMenuItem hideOutboundMenuItem;
    private javax.swing.JPopupMenu.Separator inboundSeparator;
    private javax.swing.JPopupMenu.Separator outboundSeparator;
    private javax.swing.JMenuItem pickMenuItem;
    private javax.swing.JPopupMenu.Separator pickSeparator;
    private javax.swing.JMenuItem removeElementsMenuItem;
    private javax.swing.JMenuItem showInboundMenuItem;
    private javax.swing.JMenuItem showOutboundMenuItem;
    private javax.swing.JPanel viewerPanel;
    private org.jdesktop.beansbinding.BindingGroup bindingGroup;
    // End of variables declaration//GEN-END:variables

    //
    private Element selected;
    //
    private GraphConverter graphConverter;
    // 
    private Boolean filter = Boolean.TRUE;
    //
    private GraphVisualizationLevel level = GraphVisualizationLevel.PACKAGE;
    // The set of PackageElements to build the graph
    private ContentProvider contentProvider;
    // Used for setting the graph's mouse mode
    private ModalGraphMouse graphMouse = new DefaultModalGraphMouse<Integer,Number>();
    // The viewer for the graph
    private VisualizationViewer<Element, Number> viewer;
    //
    private Graph<Element, Number> graph;

    /**
     * Uses for setting a layout
     *
     * Because layouts require a graph, this command provides access
     * to a ContentProvider's graph
     */
    public abstract class SetLayoutCommand {

        protected Graph<Element, Number> getGraph() {
            return GraphVisualizationViewer.this.graph;
        }

        public abstract Layout<Element, Number> getLayout();
    }
}
